/* 
 * Reed Solomon Encoder/Decoder 
 *
 * Copyright Henry Minsky (hqm@alum.mit.edu) 1991-2009
 * (Ported to C# by Jake Tober 2010)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE. 
 *
 * Commercial licensing is available under a separate license, please
 * contact author for details.
 *
 * Source code is available at http://rscode.sourceforge.net
 */
using Logger;
using System.Diagnostics.Contracts;

namespace RSCode
{
    internal class RS
    {
        public RS(int NPAR)
        {
            Contract.Requires(NPAR > 0 && NPAR <= 128);

            int MAXDEG = NPAR * 2;
            pBytes = new byte[MAXDEG];
            synBytes = new byte[MAXDEG];
            genPoly = new byte[MAXDEG * 2];

            galois = new Galois();
            berlekamp = new Berlekamp(MAXDEG, galois);

            compute_genpoly(NPAR, genPoly, MAXDEG);
        }

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(pBytes != null);
            Contract.Invariant(synBytes != null);
            Contract.Invariant(genPoly != null);
        }

        public void Reset()
        {
            zeroArray(pBytes);
            zeroArray(synBytes);
            berlekamp.Reset();
        }

        Galois galois;
        Berlekamp berlekamp;
        Log log = Log.Create<RS>();

        /* Encoder parity bytes */
        byte[] pBytes;

        /* Decoder syndrome bytes */
        byte[] synBytes;

        /* generator polynomial */
        byte[] genPoly;

        /// <summary>
        /// zeroes an array
        /// </summary>
        public static void zeroArray<T>(T[] buf)
        {
            Contract.Requires(buf != null);
            T defaultValue = default(T);
            for (int i = 0; i < buf.Length; i++) buf[i] = defaultValue;
        }

        /// <summary>
        /// Prints the parity bytes
        /// </summary>
        void print_parity(int NPAR)
        {
            int i;
            log.Debug("Parity Bytes: ");
            for (i = 0; i < NPAR; i++)
                log.Debug("[{0}]:{1}, ", i, pBytes[i]);
            log.Debug("");
        }

        /// <summary>
        /// Append the parity bytes onto the end of the message 
        /// </summary>
        private void build_codeword(byte[] msg, int nbytes, byte[] dst, int NPAR)
        {
            Contract.Requires(msg != null && msg.Length >= nbytes);
            Contract.Requires(dst != null && dst.Length >= nbytes + NPAR);
            int i;

            for (i = 0; i < nbytes; i++) dst[i] = msg[i];

            for (i = 0; i < NPAR; i++)
            {
                dst[i + nbytes] = pBytes[NPAR - 1 - i];
            }
        }

        /// <summary>
        /// Reed Solomon Decoder  
        /// </summary>
        /// <remarks>
        /// Computes the syndrome of a codeword. Puts the results into the synBytes[] array.
        /// </remarks>
        /// <param name="data"></param>
        /// <param name="nbytes"></param>
        /// <param name="NPAR"></param>
        public void decode_data(byte[] data, int nbytes, int NPAR)
        {
            Contract.Requires(data != null && data.Length >= nbytes);
            Contract.Requires(nbytes > NPAR);
            int i, j;
            byte sum;
            for (j = 0; j < NPAR; j++)
            {
                sum = 0;
                for (i = 0; i < nbytes; i++)
                {
                    sum = (byte)(data[i] ^ galois.gmult(galois.gexp_lookup(j + 1), sum));
                }
                synBytes[j] = sum;
            }
        }


        /* Check if the syndrome is zero */
        public int check_syndrome(int NPAR)
        {
            int i, nz = 0;
            for (i = 0; i < NPAR; i++)
            {
                if (synBytes[i] != 0)
                {
                    nz = 1;
                    break;
                }
            }
            return nz;
        }

        /// <summary>
        /// Create a generator polynomial for an n byte RS code.
        /// </summary>
        /// <remarks>
        /// The coefficients are returned in the genPoly arg. Make sure that the genPoly array which is passed in is  at least n+1 bytes long. 
        /// </remarks>
        void compute_genpoly(int nbytes, byte[] genpoly, int MAXDEG)
        {
            Contract.Requires(genpoly != null);
            Contract.Requires(MAXDEG > 0 && MAXDEG <= 256);
            Contract.Requires(nbytes >= 0);

            int i;
            byte[] tp = new byte[256];
            byte[] tp1 = new byte[256];

            /* multiply (x + a^n) for n = 1 to nbytes */

            berlekamp.zero_poly(tp1, MAXDEG);
            tp1[0] = 1;

            for (i = 1; i <= nbytes; i++)
            {
                berlekamp.zero_poly(tp, MAXDEG);
                tp[0] = galois.gexp_lookup(i);		/* set up x+a^n */
                tp[1] = 1;

                berlekamp.mult_polys(genpoly, tp, tp1, MAXDEG);
                berlekamp.copy_poly(tp1, genpoly, MAXDEG);
            }
        }

        /// <summary>
        /// Simulate a LFSR with generator polynomial for n byte RS code. 
        /// Pass in a pointer to the data array, and amount of data
        /// </summary>
        /// <remarks>
        /// The parity bytes are deposited into pBytes[], and the whole message and parity are copied to dest to make a codeword
        /// </remarks>
        public void encode_data(byte[] msg, int nbytes, byte[] dst, int NPAR)
        {
            Contract.Requires(msg != null && msg.Length >= nbytes);
            Contract.Requires(dst != null && dst.Length >= nbytes + NPAR);
            int i;
            byte[] LFSR = new byte[NPAR + 1];
            int dbyte;
            int j;

            for (i = 0; i < NPAR + 1; i++) LFSR[i] = 0;

            for (i = 0; i < nbytes; i++)
            {
                dbyte = msg[i] ^ LFSR[NPAR - 1];
                for (j = NPAR - 1; j > 0; j--)
                {
                    LFSR[j] = (byte)(LFSR[j - 1] ^ galois.gmult(genPoly[j], dbyte));
                }
                LFSR[0] = galois.gmult(genPoly[0], dbyte);
            }

            for (i = 0; i < NPAR; i++)
                pBytes[i] = LFSR[i];

            build_codeword(msg, nbytes, dst, NPAR);
        }

        public int correct_errors_erasures(byte[] codeword,
                             int ML,
                             int nerasures,
                             int[] erasures, int MAXDEG, int NPAR)
        {
            Contract.Requires(codeword != null);
            Contract.Requires(erasures != null);
            Contract.Requires(MAXDEG >= 0);
            return berlekamp.correct_errors_erasures(codeword,
                             ML,
                             nerasures,
                             erasures, MAXDEG, NPAR, synBytes);
        }
    }
}